/*
 * @(#)BasicClock.java	1.7 02/08/21
 *
 * Copyright (c) 1996-2002 Sun Microsystems, Inc.  All rights reserved.
 */

package com.sun.media;

import javax.media.Clock;
import javax.media.ClockStartedError;
import javax.media.ClockStoppedException;
import javax.media.IncompatibleTimeBaseException;
import javax.media.StopTimeSetError;
import javax.media.SystemTimeBase;
import javax.media.Time;
import javax.media.TimeBase;

/**
 * BasicClock implements javax.media.Clock.
 * This is not a running thread that implements the clock ticks.
 * It just implements the math and maintains the correct states
 * to perform the computations from media-time to time-base-time.
 * @version 1.7, 02/08/21
 **/
public class BasicClock implements Clock {

    private TimeBase master;
    private long startTime = Long.MAX_VALUE; 	// in time base time
    private long stopTime = Long.MAX_VALUE;	// in media time
    private long mediaTime = 0;		// The media time when the state of
					// the clock last changes.
    private long mediaStart = 0;	// lower bound of the mediaTime. 
    private long mediaLength = -1;	// upper bound of the mediaTime.
    private float rate = (float)1.0;

    public final static int STOPPED = 0;
    public final static int STARTED = 1;

    public BasicClock() {
	master = new SystemTimeBase();
    }


    /**
     * Set a time base on the clock.
     * All media-time to time-base-time will be computed with the
     * given time base.
     * @param master the new master time base.
     * @exception IncompatibleTimeBaseException thrown if clock cannot accept
     * the given time base.
     **/
    public void setTimeBase(TimeBase master) throws IncompatibleTimeBaseException{
	if (getState() == STARTED) {
	    throwError(new ClockStartedError("setTimeBase cannot be used on a started clock."));
	}
	if (master == null) {
	    if (!(this.master instanceof SystemTimeBase))
		this.master = new SystemTimeBase();
	} else
	    this.master = master;
    }

    /**
     * Start the clock with the given time-base-time
     * @param the time base time to start the clock.
     **/
    public void syncStart(Time tbt) {
	if (getState() == STARTED) {
	    throwError(new ClockStartedError("syncStart() cannot be used on an already started clock."));
	}
	// If the given start time is already later than now.  We'll reset
	// the clock start time to now.
	if (master.getNanoseconds() > tbt.getNanoseconds())
	    startTime = master.getNanoseconds();
	else
	    startTime = tbt.getNanoseconds();
    }

    /**
     * Stop the clock immediately.
     **/
    public void stop() {
	if (getState() == STOPPED) {
	    // It's already stopped.  No-op.
	    return;
	}

	// It's a change of state, so we'll mark the mediaTime.
	mediaTime = getMediaNanoseconds();

	// Reset the start time.
	startTime = Long.MAX_VALUE;
    }

    /**
     * Preset a stop time in media time.
     * @param t the preset stop time.
     **/
    public void setStopTime(Time t) {
	if (getState() == STARTED && stopTime != Long.MAX_VALUE) {
	    throwError(new StopTimeSetError("setStopTime() may be set only once on a Started Clock"));
	}
	stopTime = t.getNanoseconds();
    }

    /**
     * Return the preset stop time.
     * @return the preset stop time.
     **/
    public Time getStopTime() {
	return new Time(stopTime);
    }

    /**
     * Set the media time.  This will be the media presented at the
     * clock's start time.
     * @param the media time to set to.
     **/
    public void setMediaTime(Time now) {
	if (getState() == STARTED) {
	    throwError(new ClockStartedError("setMediaTime() cannot be used on a started clock."));
	}
	long t = now.getNanoseconds();
	if (t < mediaStart)
	    mediaTime = mediaStart;
	else if (mediaLength != -1 && t > mediaStart + mediaLength)
	    mediaTime = mediaStart + mediaLength;
	else
	    mediaTime = t;
    }

    /**
     * Return the current media time.
     * @return the current media time.
     **/
    public Time getMediaTime() {
	return new Time(getMediaNanoseconds());
    }

    /**
     * Get the current media time in nanoseconds.
     * @return the current media time in nanoseconds.
     */
    public long getMediaNanoseconds() {
	if (getState() == STOPPED) {
	    return mediaTime;
	}

	long now = master.getNanoseconds();
	if (now > startTime) {
	    // The media has been playing for a while.
	    long t = (long)((double)(now - startTime) * rate) + mediaTime;
	    if (mediaLength != -1 && t > mediaStart + mediaLength)
	        return mediaStart + mediaLength;
	    else
		return t;
	} else {
	    // We haven't reached the scheduled start time yet.
	    return mediaTime;
	}
    }

    /**
     * Set the lower bound of the media time.
     * @param t the lower bound of the media time.
     */
    protected void setMediaStart(long t) {
	mediaStart = t;
    }

    /**
     * Set the upper bound of the media time.
     * @param t the upper bound of the media time.
     */
    protected void setMediaLength(long t) {
	mediaLength = t;
    }

    /**
     * Return the current state of the clock in either started or stopped
     * state.
     * @return the current clock state.
     */
    public int getState() {

	// A start time has not been set.
	if (startTime == Long.MAX_VALUE)
	    return STOPPED;

	// A stop time has not been set.
	if (stopTime == Long.MAX_VALUE)
	    return STARTED;

	// The tricky case is when the clocked has started and
	// the media has reached the scheduled stop time.  In that
	// case, the clock is considered stopped.
// COMMENTED OUT BY BABU
// 	long now = master.getNanoseconds();
//         if (now > startTime) {
// 	    // The media has already been playing for some time.
		
// 	    long curMediaTime = (long)((now - startTime) * rate) + mediaTime; 
// 	    if ((rate > 0 && curMediaTime >= stopTime) ||
// 		(rate < 0 && curMediaTime <= stopTime)) {
// 	        // We have gone past the scheduled stop time.
// 		// We are in the stop state.
// 		System.out.println(this + ": BasicClock getState() SIDEEFFECT");
// 		mediaTime = validateTime(stopTime);
// 		startTime = Long.MAX_VALUE;
// 	        return STOPPED;
// 	    }
// 	}

	// In all other cases, the clock has already been started.
	return STARTED;
    }

    /**
     * Return the Sync Time.
     * Not yet implementated.
     **/
    public Time getSyncTime() {
	return new Time(0);
    }

    /**
     * Get the Time Base that the clock is currently using.
     * @return the Time Base that the clock is currently using.
     **/
    public TimeBase getTimeBase() {
	return master;
    }

    /**
     * Map the the given media-time to time-base-time.
     * @param t media time
     * @return time base time.
     * @exception ClockStoppedException is thrown if this method is invoked
     * on a stopped clock.
     **/
    public Time mapToTimeBase(Time t) throws ClockStoppedException {
	if (getState() == STOPPED) {
	    ClockStoppedException e = new ClockStoppedException();
	    Log.dumpStack(e);
	    throw e;
	}
	return new Time((long)((t.getNanoseconds() - mediaTime)/rate) + startTime);
    }

    /**
     * Set the rate of presentation: 1.0: normal, 2.0: twice the speed.
     * -2.0: twice the speed in reverse.
     * @param the speed factor.
     * @return the actual rate the clock is set to.
     **/
    public float setRate(float factor) {
	if (getState() == STARTED) {
	    throwError(new ClockStartedError("setRate() cannot be used on a started clock."));
	}
	rate = factor;
	return rate;
    }

    /**
     * Get the current presentation speed.
     * @return the current presentation speed.
     **/
    public float getRate() {
	return rate;
    }

    protected void throwError(Error e) {
	Log.dumpStack(e);
	throw e;
    }
}
